import * as Mqtt from 'async-mqtt';
import { CloudTopic } from '../cloud-topic/cloud-topic';

/**
 * cloud ws 服务
 *
 * @author chitanda
 * @date 2022-11-28 17:11:07
 * @export
 * @class CloudMQ
 */
export class CloudMQ {
  /**
   * 连接默认配置
   *
   * @author chitanda
   * @date 2022-11-28 16:11:22
   * @protected
   * @type {Mqtt.IClientOptions}
   */
  protected options: Mqtt.IClientOptions = {
    // 超时时间
    connectTimeout: 4000,
    // username: 'emqx',
    // password: 'emqx',
    // 心跳时间
    keepalive: 60,
    clean: true,
  };

  /**
   * 客户端
   *
   * @author chitanda
   * @date 2022-11-30 10:11:51
   * @protected
   * @type {Mqtt.AsyncMqttClient}
   */
  protected _client!: Mqtt.AsyncMqttClient;

  /**
   * mq 客户端实例
   *
   * @description 需先调用 connect 后，才有客户端实例
   * @author chitanda
   * @date 2022-11-29 11:11:39
   * @readonly
   * @type {Mqtt.AsyncMqttClient}
   */
  get client(): Mqtt.AsyncMqttClient {
    return this._client;
  }

  /**
   * 已经订阅的 topic
   *
   * @author chitanda
   * @date 2022-11-28 17:11:37
   * @protected
   * @type {Map<string, CloudTopic>}
   */
  protected topicMap: Map<string, CloudTopic> = new Map();

  /**
   * Creates an instance of CloudWSService.
   *
   * @author chitanda
   * @date 2022-11-28 17:11:08
   * @param {string} url mqtt 服务地址
   * @param {Mqtt.AsyncMqttClient} [opt] mqtt 配置参数
   */
  constructor(protected url: string, opt?: Mqtt.AsyncMqttClient) {
    if (opt) {
      Object.assign(this.options, opt);
    }
  }

  /**
   * 建立 mqtt 客户端连接，并监听消息
   *
   * @author chitanda
   * @date 2022-11-29 11:11:09
   * @protected
   */
  protected createClient(): Mqtt.AsyncMqttClient {
    if (!this._client) {
      this._client = Mqtt.connect(this.url, this.options);
      this.client.on('message', (topic: string, message: Buffer) => {
        let msg: string = '';
        try {
          if (this.topicMap.has(topic)) {
            const ct = this.topicMap.get(topic)!;
            msg = message.toString('utf-8');
            ct.send(msg);
          }
        } catch (error) {
          console.warn('消息中心：websocket消息解析失败!');
          console.warn('消息内容：', msg);
          console.error(error);
        }
      });
    }
    return this.client;
  }

  /**
   * 连接 mqtt 服务器
   *
   * @description 连接成功返回 null，连接失败返回异常信息
   * @author chitanda
   * @date 2022-11-28 17:11:27
   * @return {*}  {(Promise<Error | null>)}
   */
  connect(): Promise<Error | null> {
    this.createClient();
    if (this.client.connected) {
      return Promise.resolve(null);
    }
    return new Promise(resolve => {
      const connect = () => {
        console.log('mqtt 连接成功');
        this.client.off('connect', connect);
        resolve(null);
      };
      const error = err => {
        console.log('mqtt 连接失败');
        this.client.off('error', error);
        resolve(err);
      };
      this.client.on('connect', connect);
      this.client.on('error', error);
    });
  }

  /**
   * 订阅一个 topic
   *
   * @author chitanda
   * @date 2022-11-28 18:11:45
   * @param {string} topic
   * @param {Mqtt.IClientSubscribeOptions} [opt]
   * @return {*}  {Promise<CloudTopic>}
   */
  async onTopic(
    topic: string,
    opt?: Mqtt.IClientSubscribeOptions,
  ): Promise<CloudTopic> {
    if (this.topicMap.has(topic)) {
      return this.topicMap.get(topic)!;
    }
    if (opt) {
      await this.client.subscribe(topic, opt);
    } else {
      await this.client.subscribe(topic);
    }
    const cloudTopic = new CloudTopic(this.client, topic);
    this.topicMap.set(topic, cloudTopic);
    return cloudTopic;
  }

  /**
   * 取消 topic 订阅
   *
   * @author chitanda
   * @date 2022-11-28 18:11:41
   * @param {string} topic
   * @return {*}  {Promise<void>}
   */
  async unTopic(topic: string): Promise<void> {
    if (this.topicMap.has(topic)) {
      await this.client.unsubscribe(topic);
      const ct = this.topicMap.get(topic)!;
      await ct.destroyed();
    }
  }

  /**
   * 获取 topic
   *
   * @author chitanda
   * @date 2022-11-28 18:11:59
   * @param {string} topic
   * @return {*}  {(CloudTopic | undefined)}
   */
  getTopic(topic: string): CloudTopic | undefined {
    return this.topicMap.get(topic);
  }
}
